using System;
using System.Collections.Generic;
using System.Text;
using Renderer.DataStructures;

namespace Renderer.Utils
{
    class MathHelper
    {
        public const double EPS = 1e-8;
                
        public static bool SegmentIntersectsTriangle(Vector3D src, Vector3D dest, Triangle triangle, out Vector3D intersectionPoint)
        {
            Vector3D p0 = triangle.X1, p1 = triangle.X2, p2 = triangle.X3;
            Vector3D u, v, n;             // triangle vectors
            Vector3D dir, w0, w;          // ray vectors
            double r, a, b;               // params to calc ray-plane intersect
            intersectionPoint = new Vector3D();

            // get triangle edge vectors and plane normal
            u = p1 - p0;
            v = p2 - p0;
            n = u ^ v;             // cross product
            if (n == Vector3D.Zero)            // triangle is degenerate
                throw new Exception("Is not triangle");                 // do not deal with this case

            dir = dest - src;             // ray direction vector
            w0 = src - p0;
            a = -(n*w0);
            b = n * dir;
            if (Math.Abs(b) < 1e-7) return false;  // ray lies in triangle plane or ray disjoint from plane


            // get intersect point of ray with triangle plane
            r = a / b;
            if (r < -1e-6) return false;  // ray goes away from triangle => no intersect

            // for a segment, also test if (r > 1.0) => no intersect
            if (r > 1 + 1e-6) return false;

            intersectionPoint = src + r * dir;           // intersect point of ray and plane

            // is I inside T?
            double uu, uv, vv, wu, wv, D;
            uu = u * u;
            uv = u * v;
            vv = v * v;
            w = intersectionPoint - p0;
            wu = w * u;
            wv = w * v;
            D = uv * uv - uu * vv;

            // get and test parametric coords
            double s, t;
            s = (uv * wv - vv * wu) / D;
            if (s < -1e-6 || s > 1.0 + 1e-6) return false;
            t = (uv * wu - uu * wv) / D;
            if (t < -1e-6 || (s + t) > 1.0 + 1e-6) return false;

            return true;                      // I is in T
        }
        
        public static double GetIntersection(Ray3D ray, Triangle triangle, out Vector3D intersectionPoint)
        {                        
            bool intersect = SegmentIntersectsTriangle (ray.Origin, ray.Origin + ray.Direction*100000000, triangle, out intersectionPoint);
            return intersect ? (intersectionPoint - ray.Origin).Length() : -1.0;
        }

        public static double GetNearestIntersection(Ray3D ray, IList<Triangle> triangles, out Vector3D result, out Triangle triangle)
        {            
            result = new Vector3D();
            triangle = new Triangle();
            Vector3D intersectionPoint;
            double nearest = double.MaxValue;

            foreach (Triangle tr in triangles)
            {
                double currentDistance = GetIntersection(ray, tr, out intersectionPoint);
                if (currentDistance > -1e-8 && currentDistance < nearest)
                {
                    result = intersectionPoint;
                    nearest = currentDistance;
                    triangle = tr;                    
                }
            }
            return nearest;
        }
    }
}
